package org.ricks.common.db;

import org.ricks.db.*;
import org.ricks.log.Logger;
import org.ricks.utils.CollUtil;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author chenwei
 * @Title:
 * @Package
 * @Description:
 * @date 2020/12/2219:54
 */
public abstract class BaseDao<T> {

    private volatile boolean pmdKnownBroken = false;

    private int split;
    private Class<T> clazz;
    private final BeanHandler<T> beanHandler;
    private final BeanListHandler<T> beanListHandler;
    protected String insertSql;
    protected String deleteSql;
    protected String updateSql;
    protected String selectSql;
    protected String selectAllSql;
    protected String selectLimitSql;
    protected String countSql;
    public TableMeta tableMeta;

    public BaseDao() {
        init();
        beanHandler = new BeanHandler<T>( clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        beanListHandler = new BeanListHandler<T>(clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        try {
            initTable(tableMeta,true);
            if(PooledMgr.get().isSlave()) initTable(tableMeta,false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public BaseDao(Class clazz) {
        this.clazz = clazz;
        init();
        beanHandler = new BeanHandler<T>( clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        beanListHandler = new BeanListHandler<T>(clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        try {
            initTable(tableMeta,true);
            if(PooledMgr.get().isSlave()) initTable(tableMeta,false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public BaseDao(Class clazz,int split) {
        this.clazz = clazz;
        this.split = split;
        init();
        beanHandler = new BeanHandler<T>( clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        beanListHandler = new BeanListHandler<T>(clazz, new BasicRowProcessor(new RepositoryBeanProcessor()));
        try {
            initTable(tableMeta,true);
            if(PooledMgr.get().isSlave()) initTable(tableMeta,false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void init() {
        this.tableMeta = TableMetaBuilder.getTableMeta(clazz);;
        insertSql = SqlBuilder.getInsert(tableMeta);
        deleteSql = SqlBuilder.getDelete(tableMeta);
        updateSql = SqlBuilder.getUpdate(tableMeta);
        selectSql = SqlBuilder.getSelect(tableMeta);
        selectAllSql = SqlBuilder.getSelectAll(tableMeta);
        selectLimitSql = SqlBuilder.getSelectLimit(tableMeta);
        countSql = SqlBuilder.count(tableMeta);
    }


    private void initTable(TableMeta tableMeta,boolean isMaster) throws SQLException {
        String tableName = getValue(isMaster,SqlBuilder.getTableExists(tableMeta.getName()));
        if (tableName == null || !tableName.equals(tableMeta.getName())) {
            update(isMaster,SqlBuilder.getCreateTable(tableMeta));
        } else {
            BeanListHandler tableInfoHandler = new BeanListHandler<>( TableInfo.class, new BasicRowProcessor(new RepositoryBeanProcessor()));
            List<TableInfo> tableInfos = getTableInfos(isMaster,SqlBuilder.getTableColumn(tableMeta.getName()),tableInfoHandler);
            List<String> tableColumns = tableInfos.stream().map(tableInfo -> tableInfo.getColumnName()).collect(Collectors.toList());
            List<String> columns = tableMeta.getColumns().stream().map(TableMeta.ColumnMeta::getName).collect(Collectors.toList());
            tableMeta.getColumns().stream()
                    .forEach(columnMeta -> {
                        modifyColumn(isMaster,columnMeta,tableInfos);
                        if(!tableColumns.contains(columnMeta.getName())) {
                            tableColumns.remove(columnMeta.getName());
                            update(isMaster,SqlBuilder.getAddColumn(tableMeta, columnMeta));
                            if (tableMeta.getIndexKeys().contains(columnMeta)) {
                                update(isMaster,SqlBuilder.getAddColumnIndex(tableMeta, columnMeta));
                            }
                        }
                    });
            tableColumns.removeAll(columns);
            tableColumns.forEach(column -> update(isMaster,SqlBuilder.getRemoveColumn(tableMeta, column)));
        }
    }

    private void modifyColumn(boolean isMaster,TableMeta.ColumnMeta columnMeta, List<TableInfo> tableInfos) {
        tableInfos.forEach(tableInfo -> {
            if(tableInfo.getColumnName().equalsIgnoreCase(columnMeta.getName())) {
//                if(!columnMeta.getType().equalsIgnoreCase(tableInfo.getColumnType())) update(SqlBuilder.modifyColumn(this.tableMeta, columnMeta));
                try {
                    if(checkModifyColumn(columnMeta,tableInfo)) update(isMaster,SqlBuilder.modifyColumn(this.tableMeta, columnMeta));
                } catch (Exception e) {
                    Logger.warn("modify column failure , column name:{} ,  column type:{} and table type:{}",tableInfo.getColumnName(),columnMeta.getType(),tableInfo.getColumnType());
                }
                modifyColumnIndex(isMaster,columnMeta,tableInfo);
            }
        });
    }

    private boolean checkModifyColumn(TableMeta.ColumnMeta columnMeta, TableInfo tableInfo) {
        if (columnMeta.getType().equals(ColumnType.BIGINT.name()) ||  columnMeta.getType().equals(ColumnType.SMALLINT.name())
        || columnMeta.getType().equals(ColumnType.TINYINT.name()) || columnMeta.getType().equals(ColumnType.INT.name())) {
            String typeName = tableInfo.getColumnType().substring(0,columnMeta.getType().length());
            return !columnMeta.getType().equalsIgnoreCase(typeName);
        } else {
            return !columnMeta.getType().equalsIgnoreCase(tableInfo.getColumnType());
        }
    }

    private void modifyColumnIndex(boolean isMaster,TableMeta.ColumnMeta columnMeta,TableInfo tableInfo) {
        if(!columnMeta.isPrimaryKey()) {
            if (columnMeta.getIndex() == null && tableInfo.getIndexName() != null) {
                columnMeta.setIndex(tableInfo.getIndexName());
                update(isMaster,SqlBuilder.getDelColumnIndex(tableMeta, columnMeta));
            } else if (columnMeta.getIndex() != null && tableInfo.getIndexName() == null) {
                update(isMaster,SqlBuilder.getAddColumnIndex(tableMeta, columnMeta));
            }
        }
    }


    protected int update(boolean isMaster,String sql, Object... args) {
        Logger.debug("{}  {}", sql, args);
        int i;
        try {
            i = executeUpdate(isMaster, sql, args);
        } catch (Exception e) {
            Logger.warn(e.getMessage(), e);
            throw new DaoRuntimeException("dao update failure, %s", getClass().getName());
        }
        return i;
    }

    private  <E> E getValue(boolean isMaster,String sql, Object... args) {
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection(isMaster);
            ps = connection.prepareStatement(sql);
            ScalarHandler sh = new ScalarHandler<>();
            rs = executeQuery(ps, args);
            return (E)sh.handle(rs);
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao get value failure, %s", getClass().getName());
        } finally {
            close(ps,rs,isMaster);
        }
    }

    public int[] batch(boolean isMaster, String sql, Object[][] args,DbOperationEnum dbOperationEnum) {
        try {
            return batch(isMaster, sql, args);
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao batch update failure, %s");
        }
    }


    public int[] batch(boolean isMaster, String sql, Object[][] params) throws SQLException {
        Connection connection = getConnection(isMaster);
        PreparedStatement stmt = null;
        int[] rows = null;
        try {
            stmt = connection.prepareStatement(sql);
            for(int i = 0; i < params.length; ++i) {
                this.fillStatement(stmt, params[i]);
                stmt.addBatch();
            }
            rows = stmt.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            PooledMgr.get().discardConnectionFromDao(isMaster);
            stmt.close();
        }
        return rows;
    }

    private T getBean(String sql, Object... args) {
        Logger.debug("{}  {}", sql, args);
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection(false);
            ps = connection.prepareStatement(sql);
            rs = executeQuery(ps, args);
            T obj = beanHandler.handle(rs); //转成Bean
            return obj;
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao get bean failure, %s", clazz.getName());
        } finally {
            close(ps,rs,false);
        }
    }

    private void close(PreparedStatement ps,ResultSet rs, boolean isMaster) {
        try {
            PooledMgr.get().discardConnectionFromDao(isMaster);
            if(rs != null) rs.close();
            if(ps != null) ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * sql 方式查询
     * @param sql
     * @param args
     * @return
     */
    public T get(String sql, Object... args) {
        return convert(getBean(sql, args));
    }

    /**
     * 查询一条数据
     * @param obj
     * @param fileds
     * @return
     */
    public T get(T obj, String... fileds) {
        if(fileds.length == 0) {
            Object pk = TableValueBuilder.getPrimaryKeyValue(tableMeta, obj);
            return get(selectSql, pk);
        } else {
            Object[] list = Arrays.stream(fileds).map(str -> {
                return TableValueBuilder.getColumnValue(tableMeta.getColumnMetaMap().get(str), obj);
            }).toArray();
            return get(SqlBuilder.getSelect(tableMeta,fileds),list);
        }
    }


    /**
     * 查询一条数据
     * @param obj
     * @param fileds
     * @return
     */
    public List<T> queryList(T obj, String... fileds) {
        if(fileds.length == 0) {
            return list();
        } else {
            int pageIndex = 0, pageSize = 300;
            List<T> dsCollection = new ArrayList<>();
            boolean result = true;
            Object[] list = Arrays.stream(fileds).map(str -> {
                return TableValueBuilder.getColumnValue(tableMeta.getColumnMetaMap().get(str), obj);
            }).toArray();
            while (result) {
                List<Object> objs =new ArrayList<>(Arrays.asList(list));;
                objs.add(pageIndex * pageSize);
                objs.add(pageSize);
                Object[] nList = new Object[objs.size()];
                objs.toArray(nList);
                List<T> data = list(SqlBuilder.getLimitSelect(tableMeta,fileds),nList);
                if(CollUtil.isNotEmpty(data)) dsCollection.addAll(data); else result = false;
                pageIndex ++;
            }
            return dsCollection;
        }
    }

    public long count() {
        return executeRowsCount(countSql);
    }

    public long count(T obj,String... fileds){
        Object[] list = Arrays.stream(fileds).map(str -> {
            return TableValueBuilder.getColumnValue(tableMeta.getColumnMetaMap().get(str), obj);
        }).toArray();
        return count(SqlBuilder.count(tableMeta,fileds),list);
    }

    public long count(String sql,Object... args){
        return executeRowsCount(sql,args);
    }

    private int executeRowsCount(String sql, Object... bindArgs) {
        int rowCount = 0;
        /**影响的行数**/
        ResultSet resultSet = null;
        PreparedStatement stmt = null;
        Connection connection = null;
        try {
            /**从数据库连接池中获取数据库连接**/
            connection = getConnection(false);
            /**执行SQL预编译**/
            stmt = connection.prepareStatement(sql);
            if (bindArgs != null && bindArgs.length > 0) {
                /**绑定参数设置sql占位符中的值**/
                for (int i = 0; i < bindArgs.length; i++) {
                    stmt.setObject(i + 1, bindArgs[i]);
                }
            }
            resultSet = stmt.executeQuery();//执行sql
            while (resultSet.next()) {
                rowCount = resultSet.getInt(1);
            }
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
        } finally {
            try {
                PooledMgr.get().discardConnectionFromDao(false);
                stmt.close();
                resultSet.close();
            } catch (Exception e) {
                Logger.error(e.getMessage(), e);
            }
        }
        return rowCount;
    }

    public List<T> list() {
        int pageIndex = 0, pageSize = 300;
        List<T> dsCollection = new ArrayList<>();
        boolean result = true;
        while (result) {
            List<T> data = list(pageIndex * pageSize,pageSize);
            if(CollUtil.isNotEmpty(data)) dsCollection.addAll(list(pageIndex,pageSize)); else result = false;
            pageIndex ++;
        }
        return dsCollection;
    }

    public List<T> list(int start,int limit) {
        return list(selectLimitSql,new Object[]{start,limit});
    }

    private List<T> list(String sql, Object... args) {
        Logger.debug("{}  {}", sql, args);
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection(false);
            ps = connection.prepareStatement(sql);
            rs = executeQuery(ps, args);
            return beanListHandler.handle(rs);
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao list bean failure, %s");
        } finally {
            close(ps,rs,false);
        }
    }

    private <B> List<B> getTableInfos(boolean isMaster,String sql, ResultSetHandler beanHandler) {
        Logger.debug("{}  ", sql);
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection(isMaster);
            ps = connection.prepareStatement(sql);
            rs = executeQuery(ps);
            return (List<B>) beanHandler.handle(rs);
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao list bean failure, %s");
        } finally {
            close(ps,rs,isMaster);
        }
    }

    /**
     * Map List 转JSON
     * @param obj
     * @return
     */
    private T convert(T obj) {
        if (obj == null) {
            return null;
        }
        tableMeta.getColumns().stream()
                .filter(columnMeta -> {
                    try {
                        Object value = columnMeta.getField().get(obj);
                        return value instanceof Collection || value instanceof Map;
                    } catch (IllegalAccessException e) {
                        return false;
                    }
                }).forEach(columnMeta -> TableValueBuilder.jsonConvertJavaBean(columnMeta.getField(), obj));
        return obj;
    }

    protected void delete(Object key) {
        Object[] args = new Object[1];
        args[0] = key;
        update(true,deleteSql,args);
    }

    private  <E> List<E> listValue(String sql, Object... args) {
        Logger.debug("{}  {}", sql, args);
        ResultSet rs = null;
        PreparedStatement ps = null;
        try {
            ColumnListHandler ch = new ColumnListHandler();
            Connection connection = getConnection(false);
            ps = connection.prepareStatement(sql);
            rs = executeQuery(ps, args);
            return ch.handle(rs);
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException("dao list value failure, %s", getClass().getName());
        } finally {
            close(ps,rs,false);
        }
    }

    protected Connection getConnection(boolean isMaster) throws SQLException {
        return PooledMgr.get().getConnection(isMaster);
    }

    /**
     * 执行查询
     *
     * @param bindArgs 绑定的参数
     * @return List<Map<String, Object>>结果集对象
     * @throws SQLException SQL执行异常
     */
    private ResultSet executeQuery(PreparedStatement pst, Object... bindArgs) throws SQLException {
        ResultSet resultSet = null;
        try {
            /**获取数据库连接池中的连接**/
            if (bindArgs != null) {
                /**设置sql占位符中的值**/
                for (int i = 0; i < bindArgs.length; i++) {
                    pst.setObject(i + 1, bindArgs[i]);
                }
            }
            /**执行sql语句，获取结果集**/
            resultSet = pst.executeQuery();
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        return resultSet;
    }

    /**
     * 可以执行新增，修改，删除
     *
     * @param sql      sql语句
     * @param bindArgs 绑定参数
     * @return 影响的行数
     * @throws SQLException SQL异常
     */
    private int executeUpdate(boolean isMaster,String sql, Object[] bindArgs) {
        /**影响的行数**/
        int rows;
        PreparedStatement stmt = null;
        Connection connection;
        try {
            connection = getConnection(isMaster); /**从数据库连接池中获取数据库连接**/
            stmt = connection.prepareStatement(sql); /**执行SQL预编译**/
            if (bindArgs != null) {
                /**绑定参数设置sql占位符中的值**/
                for (int i = 0; i < bindArgs.length; i++) {
                    stmt.setObject(i + 1, bindArgs[i]);
                }
            }
            rows = stmt.executeUpdate();//执行sql
        } catch (Exception e) {
            Logger.error(e.getMessage(), e);
            throw new DaoRuntimeException( e.getMessage());
        } finally {
            try {
                PooledMgr.get().discardConnectionFromDao(isMaster);
                stmt.close();
            } catch (Exception e) {
                Logger.error(e.getMessage(), e);
            }
        }
        return rows;
    }

    private void fillStatement(PreparedStatement stmt, Object... params) throws SQLException {
        ParameterMetaData pmd = null;
        int i;
        if (!this.pmdKnownBroken) {
            try {
                pmd = stmt.getParameterMetaData();
                if (pmd == null) {
                    this.pmdKnownBroken = true;
                } else {
                    int stmtCount = pmd.getParameterCount();
                    i = params == null ? 0 : params.length;
                    if (stmtCount != i) {
                        throw new SQLException("Wrong number of parameters: expected " + stmtCount + ", was given " + i);
                    }
                }
            } catch (SQLFeatureNotSupportedException var9) {
                this.pmdKnownBroken = true;
            }
        }

        if (params != null) {
            for(i = 0; i < params.length; ++i) {
                if (params[i] != null) {
                    stmt.setObject(i + 1, params[i]);
                } else {
                    int sqlType = 12;
                    if (!this.pmdKnownBroken) {
                        try {
                            sqlType = pmd.getParameterType(i + 1);
                        } catch (SQLException var8) {
                            this.pmdKnownBroken = true;
                        }
                    }

                    stmt.setNull(i + 1, sqlType);
                }
            }

        }
    }

    public abstract void save(T... objects);

    public abstract void delete(T... objects);

    public abstract void update(T... objects);

}
